package simpleGeom;

import java.awt.geom.Line2D;
import java.util.Vector;

import processing.core.PGraphics;
import processing.core.PVector;

public class HEdge implements HasBounds2D, HasLine2D {
	public int generation = 0;
	private HEdge next;
	private HEdge sym;
	private HENode org;
	private HEdge up;
	private HEdge down;

	private Object data;
	private Object face;
	public Rect2D bounds;

	public boolean mark;

	public void setBounds() {
		bounds = new Rect2D(Math.min(x1(), x2()), Math.min(y1(), y2()),
				Math.max(x1(), x2()), Math.max(y1(), y2()));
	}

	public void display(PGraphics g) {
		g.line(x1(), y1(), x2(), y2());
	}

	public Object getData() {
		return data;
	}

	public float getLength() {
		return org.dist(this.getDest());
	}

	public void setData(Object data) {
		this.data = data;
	}
	public Vec2D getCenter2D(){
		return new Vec2D((x1()+x2())*0.5f,(y1()+y2())*0.5f);
	}
	public double getLengthSqr() {
		double dX = x2() - x1();
		double dY = y2() - y1();
		return dX * dX + dY * dY;
	}

	public double getAngle() {
		double dX = x2() - x1();
		double dY = y2() - y1();
		return Math.atan2(dY, dX);
	}

	public HEdge() {

	}

	public HEdge(HENode p1, HENode p2) {
		HEdge h1 = constructHEdge();

		this.sym = h1;
		h1.sym = this;
		this.next = h1;
		h1.next = this;

		this.org = p1;
		this.sym.org = p2;
		setBounds();
		this.sym.bounds = bounds;
	}

	public HEdge constructHEdge() {
		return new HEdge();
	}

	public void makeSingle() {
		this.next = sym;
		sym.next = this;

	}

	private void attachHE() {

		HEdge he1 = ((HENode) getOrg()).getHEdge();
		if (he1 != null)
			he1.connectOrgToEdge(this);
		else
			getOrg().setHEdge(this);
	}

	private void connectOrgToEdge(HEdge newEdge) {
		newEdge.org = this.getOrg();
		double maxDeltaAngle = getAngleBetweenCCW(getX1(), getY1(), newEdge
				.getSym().getX1(), newEdge.getSym().getY1(), getSym().getX1(),
				getSym().getY1());

		HEdge cEdge = this.getNextO();
		HEdge rightEdge = this;
		// clockwise
		while (!cEdge.equals(this)) {
			double cDeltaAngle = getAngleBetweenCCW(getX1(), getY1(), newEdge
					.getSym().getX1(), newEdge.getSym().getY1(), cEdge.getSym()
					.getX1(), cEdge.getSym().getY1());

			if (cDeltaAngle >= maxDeltaAngle) {
				rightEdge = cEdge;
				maxDeltaAngle = cDeltaAngle;
			}
			cEdge = cEdge.getNextO();

		}

		rightEdge.getPrevO().getSym().next = newEdge;
		newEdge.getSym().next = rightEdge;

	}

	private void detachHE() {
		HENode org = (HENode) this.org;
		if (org.getHEdge() == this) {
			HEdge nextHE = this.getNextO();
			if (nextHE != this) {
				org.setHEdge(nextHE);
			} else {
				org.setHEdge(null);
			}
		}
	}

	private void detachHE(HEdge newHEdge) {

		if (newHEdge != this) {
			org.setHEdge(newHEdge);
		} else {
			org.setHEdge(null);
		}

	}

	public void detach2() {

		System.out.println("start detach");
		if (this.getNext() == this.sym) {
			System.out.println("same");
		}
		if (this.sym.getNext() == this) {
			System.out.println("same2");
		}

		HEdge prevEdge1 = this.getPrev();
		System.out.println("prevEdge1");
		HEdge nextEdge1 = this.getSym().getNext();
		System.out.println("nextEdge1");
		HEdge prevEdge2 = this.getSym().getPrev();
		System.out.println("prevEdge2");
		HEdge nextEdge2 = this.getSym().getSym().getNext();
		System.out.println("nextEdge2");
		// new Org
		HEdge replace1 = null;
		if (this.getOrg().getHEdge() == this) {
			replace1 = this.getNextO();
		}
		HEdge replace2 = null;
		if (this.getDest().getHEdge() == this.sym) {
			replace2 = this.sym.getNextO();
		}
		if (replace1 != null) {
			detachHE(replace1);
		}
		if (replace2 != null) {
			this.sym.detachHE(replace2);
		}
		System.out.println("detached");
		/*
		 * detachHE(); this.sym.detachHE();
		 */

		prevEdge1.next = nextEdge1;
		prevEdge2.next = nextEdge2;

	}

	public void attach() {
		detach();
		this.org.attachHE(this);
		sym.org.attachHE(sym);
	}

	public void detach() {
		this.org.detachHE(this);
		sym.org.detachHE(sym);
	}

	public void attach2() {
		makeSingle();
		attachHE();
		this.sym.attachHE();
	}

	public float x1() {
		return org.getX();
	}

	public float y1() {
		return org.getY();
	}

	public float z1() {
		return org.getZ();
	}

	public float x2() {
		return getDest().getX();
	}

	public float y2() {
		return getDest().getY();
	}

	public float z2() {
		return getDest().getZ();
	}

	public float cX() {
		return (x1() + x2()) * 0.5f;
	}

	public float cY() {
		return (y1() + y2()) * 0.5f;
	}

	public float cZ() {
		return (z1() + z2()) * 0.5f;
	}

	public float getX1() {
		return org.getX();
	}

	public float getX2() {
		return this.getDest().getX();
	}

	public float getY1() {
		return org.getY();
	}

	public float getY2() {
		return this.getDest().getY();
	}

	public HENode getOrg() {
		return org;
	}

	public void setOrg(HENode org) {
		this.org = org;
	}

	public void setDest(HENode dest) {
		this.sym.org = dest;
	}

	public HENode getDest() {
		return this.getSym().getOrg();
	}

	public HEdge getNext() {
		return next;
	}

	public HEdge getPrevO() {

		HEdge nextEdge = this;
		while (nextEdge.getNextO() != this) {

			nextEdge = nextEdge.getNextO();

		}
		return nextEdge;
	}

	public HEdge getPrev() {

		return this.getPrevO().getSym();
		/*
		 * HEdge nextEdge=this.getNext(); while
		 * (!nextEdge.getNext().equals(this)){ nextEdge=nextEdge.getNext(); }
		 * return nextEdge;
		 */
	}

	public HEdge getNextO() {
		return this.sym.next;
	}

	public HEdge getSym() {
		return sym;
	}

	public void setFace(Object face) {
		this.face = face;
	}

	public void setFaceToAll(Object face) {
		HEdge nHEdge = this;
		do {
			nHEdge.setFace(face);
			nHEdge = nHEdge.getNext();
		} while (nHEdge != this);
	}

	public Object getFace() {
		return face;
	}

	public Vector<HENode> getFaceNodes() {
		Vector<HENode> nodes = new Vector<HENode>();
		HEdge nHEdge = this;
		do {
			nodes.add(nHEdge.getOrg());
			nHEdge = nHEdge.getNext();
		} while (!nHEdge.equals(this));
		return nodes;
	}

	public HEdge split(HENode p) {
		Object face1 = this.getFace();
		Object face2 = this.getSym().getFace();
		HENode p2 = this.getDest();
		this.detach();
		this.setDest(p);
		this.attach();
		HEdge newHEdge = new HEdge(p, p2);
		newHEdge.attach();
		newHEdge.setFace(face1);
		newHEdge.getSym().setFace(face2);
		return newHEdge;
	}

	public Vec2D getDir() {
		return Vec2D.sub(getDest(), getOrg());
	}

	private static double getAngleBetweenCCW(double px, double py, double ax,
			double ay, double bx, double by) {
		double vax = px - ax;
		double vay = py - ay;
		double vbx = px - bx;
		double vby = py - by;
		double cAngle = Math.atan2(vby, vbx) - Math.atan2(vay, vax);
		if (cAngle < 0) {
			return (cAngle + 2.0 * Math.PI);
		}
		return cAngle;
	}

	private static double getAngleBetweenCCW(PVector p, PVector a, PVector b) {
		double vax = p.x - a.x;
		double vay = p.y - a.y;
		double vbx = p.x - b.x;
		double vby = p.y - b.y;
		double cAngle = Math.atan2(vby, vbx) - Math.atan2(vay, vax);
		if (cAngle < 0) {
			return (cAngle + 2.0 * Math.PI);
		}
		return cAngle;
	}

	public Rect2D getBounds() {
		return bounds;
	}

	public void setNext(HEdge next) {
		this.next = next;
	}

	public Rect2D getBounds2D() {
		// TODO Auto-generated method stub
		return getBounds();
	}

	public Vec2D lineIntersection(float cX, float cY, float dX, float dY) {
		float aX = getX1();
		float aY = getY1();
		float bX = getX2();
		float bY = getY2();
		float denominator = ((bX - aX) * (dY - cY)) - ((bY - aY) * (dX - cX));

		if (denominator == 0)
			return null;// parallel

		float numerator = ((aY - cY) * (dX - cX)) - (aX - cX) * (dY - cY);
		float numerator2 = ((aY - cY) * (bX - aX)) - ((aX - cX) * (bY - aY));

		float r = numerator / denominator;
		float s = numerator2 / denominator;

		// if (s <= 0 || s >= 1 || r <= 0 || r >= 1)
		if (r < 0 || r >= 1)
			return null;// colinear
		if (r == 0)
			return this.getOrg();
		float x = aX + r * (bX - aX);
		float y = aY + r * (bY - aY);
		return new Vec2D(x, y);
	}

	public double ptLineDist(double px, double py) {
		return Line2D.ptLineDist(x1(), y1(), x2(), y2(), px, py);
	}

	public Vec2D rayIntersection(float pX, float pY, float dX, float dY,
			float tol) {
		float aX = getX1();
		float aY = getY1();
		float bX = getX2();
		float bY = getY2();
		float denominator = ((bX - aX) * (dY)) - ((bY - aY) * (dX));

		if (denominator == 0) {

			double lineDist = ptLineDist(pX, pY);
			if (lineDist < tol) {
				float fA = 0;
				float fB = 0;
				if (dX != 0) {
					fA = (aX - pX) / dX;
					fB = (bX - pX) / dX;
				} else {
					fA = (aY - pY) / dY;
					fB = (bY - pY) / dY;
				}
				if (fA < 0 || fB < 0)
					return null;
				float minF = Math.min(fA, fB);
				return new Vec2D(minF * dX + pX, minF * dY + pY);
			} else
				return null;// parallel
		}

		float numerator = ((aY - pY) * (dX)) - (aX - pX) * (dY);
		float numerator2 = ((aY - pY) * (bX - aX)) - ((aX - pX) * (bY - aY));

		float r = numerator / denominator;
		float s = numerator2 / denominator;

		if (r < 0 || r >= 1)
			return null;// not intersecting
		if (s < 0)
			return null;

		float x = aX + r * (bX - aX);
		float y = aY + r * (bY - aY);

		return new Vec2D(x, y);
	}

	public String toString() {
		String s = super.toString();
		return s + "x1 " + x1() + "y1 " + y1() + "x2 " + x2() + "y2 " + y2();
	}
}